package com.db.httpserver;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Arrays;
import java.util.Iterator;

public class NioServer {

    public static void main(String... args) throws IOException {
        Selector selector = Selector.open();
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.socket().bind(new java.net.InetSocketAddress(8000));
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        while (true) {
            selector.select();
            Iterator iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()) {
                SelectionKey key = (SelectionKey) iterator.next();
                if (key.isAcceptable()) {
                    SocketChannel clientSocketChannel = serverSocketChannel.accept();
                    clientSocketChannel.configureBlocking(false);
                    clientSocketChannel.register(selector, SelectionKey.OP_READ);
                } else if (key.isReadable()) {
                    SocketChannel clientSocketChannel = (SocketChannel) key.channel();
                    System.out.println("Accepted connection from " + clientSocketChannel);
                    clientSocketChannel.configureBlocking(false);

                    byte[] clientRequest = readBytes(clientSocketChannel);

                    final Request request = new RequestScanner(new ByteArrayInputStream(clientRequest)).scan();

                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
                    PrintStream ps = new PrintStream(baos);

                    new Worker().handleRequest(request, ps);

                    SelectionKey key2 = clientSocketChannel.register(selector, SelectionKey.OP_WRITE);
                    key2.attach(baos.toByteArray());
                } else if (key.isWritable()) {
                    SocketChannel clientSocketChannel = (SocketChannel) key.channel();
                    ByteBuffer output = ByteBuffer.wrap((byte[]) key.attachment());
                    System.err.println(output.toString());
                    if (!output.hasRemaining()) {
                        output.rewind();
                    }
                    clientSocketChannel.write(output);
                    clientSocketChannel.close();
                }
                iterator.remove();
            }
        }
    }

    private static byte[] readBytes(SocketChannel clientSocketChannel) throws IOException {
        byte[] clientRequest = new byte[0];
        ByteBuffer buf = ByteBuffer.allocate(1024);
        int bytesRead = clientSocketChannel.read(buf);
        while (bytesRead > 0) {
            buf.flip();
            clientRequest = Arrays.copyOf(clientRequest, clientRequest.length + bytesRead);

            System.arraycopy(buf.array(), 0, clientRequest, clientRequest.length - bytesRead, bytesRead);
            buf.clear();
            bytesRead = clientSocketChannel.read(buf);
        }
        return clientRequest;
    }
}
